
import { parentPort, workerData } from 'worker_threads';
import fs from 'fs';
import path from 'path';
import config from 'config';
import { TradingStrategy, sample, DataHandler, MergeData, PeriodProcess, Period, CustomIndicators, SimilarityMetrics, MixIndicatorsTolerance, isSimilarTo } from '../../trader';
import { KLineDTO } from '../kline-history/KLineHistory.entity';

import { autoToFixed, keepDecimalFixed, transformCSVToJSON, transformToCSV } from '../../utils';
import { outLogger } from '../../common/logger';
import dayjs from 'dayjs';
import { VectorData } from '../symbol-factor/vector.worker';
import { omit } from 'lodash';


const publicPath = config.get('publicPath') as string;
const vectorDir = path.join(publicPath, 'vectors');
let history: KLineDTO[] = [];
const removeKeys: Array<keyof CustomIndicators | 'buy' | 'sell' | 'symbol' | 'isMock'> = [
  'buy',
  'sell',
  'symbol',
  'isMock',
  'vector_4h',
  'vector_1d',
  'vector_stort_15m',
  'vector_stort_4h',
  'vector_stort_1d',
  'statistics_4h_max',
  'statistics_4h_min',
  'statistics_1d_max',
  'statistics_1d_min',
]
if (parentPort) {
  sample.symbolVectors = workerData.symbolVectors;
  history = workerData.history;
  // 在主进程或主线程中
  parentPort.on('message', msg => {
    const result = toStrategData(msg);
    parentPort.postMessage(result);
  });
} else {
  // 在 fork 或 worker 中
  process.on("message", msg => {
    if (msg.type && msg.type === 'workerData') {
      sample.symbolVectors = msg.workerData.symbolVectors;
      history = msg.workerData.history;
      return;
    }
    const result = toStrategData(msg);
    // 将结果发送给主线程
    process.send(result);
  });
}


export function toStrategData({symbol, period, strategyStr}: {symbol: string, period?: Period, strategyStr: string}) {

  const period_list_15m: any[] = [];
  const periodList: any[] = [];
  const marks: {
    symbol: string,
    timestamp: number;
    close: number;
    action: string;
    label_text?: string;}[] = []

  let last_feature_vector_detail: CustomIndicators & {timestamp?: number}

  // 指标
  const tradingStrategy = new TradingStrategy({isMock: true});

  tradingStrategy
  .addCustomStrategy('worker', symbol, {
    code: strategyStr,
  }).onSignal((signal, kline) => {
    marks.push({
      symbol: kline.symbol,
      timestamp: kline.timestamp,
      close: kline.close,
      action: signal,
      label_text: kline.label_text
    })
  });
  tradingStrategy.onMixIndicator((bar, feature_vector_detail) => {
    last_feature_vector_detail = {
      ...feature_vector_detail,
      timestamp: bar.timestamp,
    }
    period_list_15m.push({
      close: bar.close,
      open: bar.open,
      high: bar.high,
      low: bar.low,
      timestamp: bar.timestamp,
      volume: bar.volume,
      buy: bar.buy,
      sell: bar.sell,
      ...omit(feature_vector_detail, removeKeys)
    });
  });
  
  const dataHander = new DataHandler('mock');

  dataHander.on('interval:15m', (data) => {
    tradingStrategy.push(data, '15m');
  });
  dataHander.on('interval:4h', (data) => {
    tradingStrategy.push(data, '4h');
  });
  dataHander.on('update:4h', (data) => {
    tradingStrategy.update(data, '4h');
  });
  dataHander.on('interval:1d', (data) => {
    tradingStrategy.push(data, '1d');
  });
  dataHander.on('update:1d', (data) => {
    tradingStrategy.update(data, '1d');
  });


  history.forEach(item => {
    dataHander.handleHistory(item)
  });

  if (!period || period == '15m') {
    return {
      periodList: transformToCSV(period_list_15m),
      marks: transformToCSV(marks),
      predictedData: ''
    }
  }
  const periodProcess = new PeriodProcess<MergeData & { feature_vector_detail: CustomIndicators}>([period], 1);
  
  periodProcess.onPeriod((period, data) => {
    if (data) {
      periodList.push(data);
    }
  });

  period_list_15m.forEach(({symbol, close, open, high, low, volume, timestamp, label, buy, sell, asks, bids, isMock, ...others}) => {
    periodProcess.process(timestamp, (mergeData) => {
      mergeData.symbol = symbol;
      mergeData.close = Number(close);
      mergeData.high =  mergeData.high == undefined ? high : Math.max(mergeData.high, high);
      mergeData.low = mergeData.low == undefined ? low : Math.min(mergeData.low, low);
      mergeData.volume = mergeData.volume === undefined ? volume : mergeData.volume + volume;
      Object.assign(mergeData, others);
      if (mergeData.open === undefined) {
        mergeData.open = open;
      }

      mergeData.buy = mergeData.buy === undefined ?  buy : mergeData.buy + buy;
      mergeData.sell = mergeData.sell === undefined ?  sell : mergeData.sell + sell;
    });
  });
  periodProcess.end();
  // 组件
  let vectors:(VectorData & {
    similarity?: number;
  })[] = [];

  try {
    const str = fs.readFileSync(path.resolve(vectorDir, `${symbol}_vectors_${period}.csv`), { encoding: 'utf-8' });
    vectors = transformCSVToJSON(str);
  } catch (error) {
    outLogger.error(error)
    // const str = fs.readFileSync(path.resolve(vectorDir, `ETHUSDT_vectors_${period}.csv`), { encoding: 'utf-8' });
    // vectors = transformCSVToJSON(str);
  }

  const predictedData = []
  let matchKline:  (VectorData & {
    similarity?: number;
  });

  const period_vector_list = period === '4h' 
  ? last_feature_vector_detail.vector_4h 
  : last_feature_vector_detail.vector_1d;

  const predictedPoint = period === '4h' 
  ? SimilarityMetrics.manhattanSimilarityTop(
    vectors, 
    period_vector_list, 
    (data: VectorData) => isSimilarTo(data.close_ma_rate, last_feature_vector_detail.close_ma_rate_4h, 1)
  ) 
  : SimilarityMetrics.manhattanSimilarityTop(
    vectors,
    period_vector_list,
    (data: VectorData) => isSimilarTo(data.close_ma_rate, last_feature_vector_detail.close_ma_rate_1d, 1)
  );

  outLogger.info(`vectors.length: ${vectors.length}`, `period_vector_list.lenght: ${period_vector_list.length}`)
  if (!predictedPoint.length) {
    outLogger.error(`predictedPoint`, predictedPoint)
  }

  if (predictedPoint.length > 0) {
    const max_count = period_vector_list.length;
    let count = max_count;

    for (let i = 0; i < vectors.length; i++) {
      const element = vectors[i];
      if (count === 0) {
        outLogger.info('-> ', new Date(element.timestamp).toLocaleString(), element.vector.length)
        matchKline = element;
  
        matchKline.similarity = predictedPoint[0].similarity
        break;
      }
      if (element.timestamp > predictedPoint[0].timestamp) {
        // console.log(new Date(element.timestamp).toLocaleString(), element.timestamp)
        count--;
      }
    }

    const currentDis = last_feature_vector_detail[`statistics_${period}_max`] - last_feature_vector_detail[`statistics_${period}_min`]
    const position = period_vector_list[period_vector_list.length - 1] - matchKline.vector[matchKline.vector.length - max_count];
    const realDis = (currentDis + (matchKline.max - matchKline.min)) / 2;
    
  //  console.log(`
  //  scaleRate: ${scaleRate}
  //  last_vector: ${period_vector_list[period_vector_list.length - 1]}
  //  ${matchKline.vector.length} - ${max_count} = ${matchKline.vector.length - max_count}
  //  matchKlineLast: ${matchKline.vector[matchKline.vector.length - max_count]}
  //  `)

    for (let i = matchKline.vector.length - 1; i >= 0; i--) {
      const vectorValue = position + matchKline.vector[i];
    
      const original_close = autoToFixed(vectorValue * realDis + last_feature_vector_detail[`statistics_${period}_min`]);
      if (matchKline.vector.length - i > max_count) {
        break;
      }
      predictedData.unshift({
        close: original_close,
        open: original_close,
        high: keepDecimalFixed(original_close * 1.02, 4),
        low: keepDecimalFixed(original_close * 0.98, 4),
        timestamp: last_feature_vector_detail.timestamp,
        similarity: keepDecimalFixed(matchKline.similarity, 3),
        label_text: dayjs(predictedPoint[0].timestamp).format('YYYY-MM-DD'),
      });
    }
    // console.log(`predictedData last`, predictedData[predictedData.length - 1])
  }

  // 删除无用的指标，减少体积
  for (let i = 0; i < periodList.length; i++) {
    const element = periodList[i];
    removeKeys.forEach(key => {
      delete element[key]
    })
  }

  return {
    periodList: transformToCSV(periodList),
    marks: transformToCSV(marks),
    predictedData: transformToCSV(predictedData),
  }

}
